1 module hip.jni.helper.jnicall;
2 import hip.util.conv:to;
3 import hip.util.format;
4 import hip.util.string;
5 import std.traits : isArray;
6 import hip.jni.jni;
7 
8 version(Android):
9 
10 
11 enum javaRep =
12 [
13     "byte"  : "B",
14     "ubyte" : "B",
15     "char"  : "C",
16     "bool"  : "Z",
17     "int"   : "I",
18     "uint"  : "I",
19     "void"  : "V",
20     "long"  : "J",
21     "ulong" : "J",
22     "float" : "F",
23     "double": "D",
24     "short" : "S",
25     "ushort": "S",
26     "string": "Ljava/lang/String;",
27     "Object": "Ljava/lang/Object;"
28 ];
29 
30 enum javaGetTypeRepresentation(T)()
31 {
32     static if(isArray!T && !is(T == string))
33     {
34         return "["~javaGetTypeRepresentation!(typeof(T.init[0]));
35     }
36     else
37     {
38         static assert((T.stringof in javaRep) !is null, "Type name "~T.stringof~" not found when searching for java type representation");
39         return javaRep[T.stringof];
40     }
41 }
42 
43 string javaGetType(T)()
44 {
45     long ind = T.stringof.countUntil("[");
46     string ret = T.stringof;
47     if(ind != -1)
48         ret = ret[0..ind];
49     switch(ret)
50     {
51         case "string":
52             ret = "Ljava/lang/String;";
53             break;
54         case "Object":
55             ret = "LJava/lang/Object;";
56             break;
57         case "uint":
58             ret = "int";
59             break;
60         case "bool":
61             ret~= "ean";
62             break;
63         case "ushort":
64             ret = "short";
65             break;
66         case "ulong":
67             ret = "long";
68             break;
69         default:break;
70     }
71     return ret;
72 }
73 
74 private string getArgs(Args...)()
75 {
76     string s;
77     static foreach(i, a; Args)
78     {
79         static if(i < Args.length - 1)
80             s~= a.stringof~",";
81         else    
82             s~= a.stringof;
83     }
84     return (s.length > 0 ? (","~s) : "") ;
85 }
86 
87 string javaGetClassPath(string path)
88 {
89     string className;
90 
91     bool hasClass = false;
92 
93     for(int i = 0; i < path.length; i++)
94     {
95         if(path[i] == '.')
96         {
97             if(hasClass)
98                 break;
99             className~="/";
100         }
101         else
102         {
103             if(isUpperCase(path[i]))
104                 hasClass = true;
105             className~=path[i];
106         }
107     }
108     return className;
109 }
110 
111 jclass javaGetClass(JNIEnv* env, string path)
112 {
113     path = javaGetClassPath(path);
114     if(path == "" || env == null)
115         return null;
116     return (*env).FindClass(env, path.toStringz);
117 }
118 string javaGetMethodName(string where)
119 {
120     long ind = lastIndexOf(where, '.');
121     bool isMethodOnly = ind == -1;
122 
123     if(isMethodOnly)
124         return where;
125     else
126         return where[ind+1..$];
127 }
128 string javaGenerateMethodName(alias javaPackage)(string method)
129 {
130     string packName;
131     string pName = javaPackage._packageName;
132     for(ulong i = 0; i < pName.length; i++)
133     {
134         if(pName[i] == '.')
135             packName~= '_';
136         else if(pName[i] == '_')
137             packName~= "_1";
138         else
139             packName~= pName[i];
140     }
141     return "Java_"~packName~"_"~method;
142 }
143 
144 enum D_TO_JAVA_TYPE_TABLE = 
145 [
146     "ubyte"  : "jboolean",
147     "byte"   : "jbyte",
148     "ushort" : "jchar",
149     "short"  : "jshort",
150     "int"    : "jint",
151     "long"   : "jlong",
152     "float"  : "jfloat",
153     "double" : "jdouble",
154     "string" : "jstring"
155 ];
156 
157 D javaTypeToD(D, J)(JNIEnv* env, J value)
158 {
159     enum javaType = javaGetType!D;
160     enum javaTypeUpper = toUpper(javaType[0]) ~ javaType[1..$];
161 
162     static if(is(D == Object))
163         return cast(void*)value;
164     else static if(is(D == string))
165     {
166         const(char)* chs = (*env).GetStringUTFChars(env, value, null);
167         string str = to!string(chs);
168         (*env).ReleaseStringUTFChars(env, value, chs);
169         return str;
170     }
171     else static if(!isArray!D)
172         return cast(D)value;
173     else
174     {
175         jarray obj = value;
176 
177         mixin(format!q{
178             j%s* javaArr = (*env).Get%sArrayElements(env, cast(j%sArray)obj, null);
179         }(javaType, javaTypeUpper, javaType));
180         int arrL = (*env).GetArrayLength(env, obj);
181         D ret;
182         alias D_singleType = typeof(D.init[0]);
183         static if(D.length == 0)
184             ret.length = arrL;
185 
186         for(int i = 0; i < arrL; i++)
187             ret[i] = cast(D_singleType)javaArr[i];
188 
189         mixin(format!q{(*env).Release%sArrayElements(env, obj, javaArr, 0);}(javaTypeUpper));
190         return ret;
191     }
192 }
193 
194 string java_dType_to_javaType(string dType)()
195 {
196     static assert((dType in D_TO_JAVA_TYPE_TABLE) != null, "Type "~dType~" not found in D_TO_JAVA table");
197     return D_TO_JAVA_TYPE_TABLE[dType];
198 }
199 
200 string java_convertParameterToD(string typeName, string varName)()
201 {
202     enum isTypeArray = typeName.countUntil("[") != -1;
203 
204     static if(typeName.countUntil("string") != -1)
205         return format!q{javaTypeToD!string(env, %s)}(varName);
206     else static if(isTypeArray)
207         return format!q{javaTypeToD!(%s)(env, %s)}(typeName, varName);
208     else
209         return varName;
210 }
211 
212 string java_getParametersDef(string funcParams)()
213 {
214     string ret = "";
215     enum string[] argsSplit = funcParams.split(",");
216     static foreach(i, v; argsSplit)
217     {
218         if(i != 0)
219             ret~=",";
220         ret~= java_dType_to_javaType!(argsSplit[i].split(" ")[0..$-1].join) //jstring, jboolean
221         ~ " "~argsSplit[i].split(" ")[$-1]; //Variables names is the last after the split
222     }
223     return ret;
224 }
225 
226 string java_getParametersCall(string funcParams)()
227 {
228     string paramsCall = "";
229     enum string[] paramsTemp = funcParams.split(",");
230 
231     static foreach(i, v; paramsTemp)
232     {
233         static if(i != 0)
234             paramsCall~=",";
235         mixin(format!q{enum string[] typeAndName_%s = v.split(" ");}(i));
236         mixin(format!q{enum string type_%s = typeAndName_%s[0..$-1].join;}(i, i));
237         mixin(format!q{enum string name_%s = typeAndName_%s[$-1];}(i, i));
238 
239         //Last string after space is the argument name
240         paramsCall~= java_convertParameterToD!(
241             mixin(format!q{type_%s}(i)), //Get type_0, type_1, type_n
242             mixin(format!q{name_%s}(i)) ////Get name_0, name_1, name_n
243         );
244     }
245     return paramsCall;
246 }
247 
248 
249 /**
250 *   This function should provide the module to import hip.for it being able to get the type.
251 *   Currently only supports basic types
252 */
253 string javaGenerateMethod(alias javaPackage, string funcSymbol, string m = __MODULE__)()
254 {
255     static assert(__traits(hasMember, javaPackage, "_packageName"), "JavaFunc error: "~javaPackage.stringof~" is not a java package");
256     mixin("import "~m~";");
257 
258 
259     enum metName = javaGenerateMethodName!(javaPackage)(funcSymbol);
260     enum funcData = typeof(mixin(funcSymbol)).stringof;
261 
262     enum firstParensIndex = funcData.countUntil("(");
263     enum string funcRet = funcData[0..firstParensIndex];
264     enum string funcParams = funcData[firstParensIndex+1..$-1];
265 
266     enum string funcParamsConverted = java_getParametersDef!(funcParams);
267     enum string paramsCall = java_getParametersCall!(funcParams);
268     
269 
270 
271     return format!q{
272         export extern(C) %s %s (JNIEnv* env, jclass clazz, %s)
273         {
274             pragma(inline, true)
275             %s(%s);
276         }
277     }(funcRet, metName, funcParamsConverted, funcSymbol, paramsCall);
278 }
279 
280 /**
281 *
282 *   It would pretty much work as if extern(Java) existed. Generates the java side names function
283 *   definitions for functions marked with @JavaFunc
284 *   Don't yet support functions with underlines (I think that the mangling is _1, but need to test)
285 */
286 mixin template javaGenerateModuleMethodsForPackage(alias javaPackage, alias module_, bool showGeneration = false)
287 {
288     ///Must mix the hasUDA trait.
289     import std.traits:hasUDA;
290     static foreach(m;  __traits(allMembers, module_))
291     {
292         static if(hasUDA!(mixin(m), JavaFunc!javaPackage))
293         {
294             static if(showGeneration)
295             {
296                 pragma(msg, m,":");
297                 pragma(msg, javaGenerateMethod!(javaPackage, m));
298             }
299             mixin(javaGenerateMethod!(javaPackage, m));
300         }
301     }
302 }
303 
304 /**
305 *   By not using templated struct, no instantiation will be required, and it will 
306 *   be easier for searching when using __traits
307 */
308 struct JavaFunc_{string packageName;}
309 
310 enum JavaFunc(alias T)()
311 {
312     static assert(__traits(hasMember, T, "_packageName"), "JavaFunc error: "~T.stringof~" is not a java package");
313     return JavaFunc_(T._packageName);
314 }
315 
316 template javaGetPackage(string packageName)
317 {
318     immutable(string) _packageName = packageName;
319 
320 
321     
322     auto javaCall(T, string path, Args...)()
323     {
324         JNIEnv* env = _env;
325         static if(is(T == void*))
326             return javaCall!(Object, path, Args);
327         static if(packageName[$-1] != '.' && path[0] != '.')
328             enum where = packageName~"."~path;
329         else
330             enum where = packageName~path;
331         enum s = getArgs!Args;
332         string typeRepresentation = "(";
333         
334         enum javaType = javaGetType!T;
335         enum javaTypeUpper = toUpper(javaType[0]) ~ javaType[1..$];
336 
337 
338         foreach (i, a; Args)
339         {
340             typeRepresentation~= javaGetTypeRepresentation!(typeof(a));
341             if(i < Args.length - 1)
342                 typeRepresentation~=",";
343         }
344         typeRepresentation~=")"~javaGetTypeRepresentation!T;
345         
346 
347         jclass cls = javaGetClass(env, where);
348         jmethodID id = (*env).GetStaticMethodID(env, cls,
349         javaGetMethodName(where).toStringz, typeRepresentation.toStringz);
350 
351         static if(is(T == Object))
352         {
353             jobject obj = mixin(q{(*env).CallStaticObjectMethod(env, cls, id } ~s ~")");
354             return cast(void*)obj;
355         }
356         else static if(is(T == string))
357         {
358             jstring obj = mixin(q{(*env).CallStaticObjectMethod(env, cls, id } ~ s~")");
359             return javaTypeToD!T(env, obj);
360         }
361         else static if(!isArray!T)
362             return cast(T)mixin(q{(*env).CallStatic}~javaTypeUpper~q{Method(env, cls, id } ~s~")");
363         else
364         {
365             jarray obj = mixin(q{(*env).CallStaticObjectMethod(env, cls, id } ~ s~")");
366             return javaTypeToD!T(env, obj);
367         }
368     }
369 
370 }
371 
372 
373 // mixin template ExportJavaFuncs(alias module_, alias javaPackage)
374 // {
375 
376 // }
377 
378 void JNISetEnv(JNIEnv* env){_env = env;}
379 private __gshared JNIEnv* _env = null;